package com.lgj.demo0812.algorithm;


import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author lizy51
 * @date 2023-02-20 09:54
 * desc:漏斗算法
 */
public class FunnelRateLimiter {

    /**
     * record a logic key and funnel
     */
    private static Map<String, Funnel> funnelMap = new ConcurrentHashMap<>();


    static class Funnel {
        private ReentrantLock lock = new ReentrantLock();


        // 水流速度
        private BigDecimal waterRate;

        // 漏斗容量
        private Integer capacity;

        // 漏斗剩余容量
        private Integer leftCapacity;

        // 上一次访问时间戳
        private Long lastAccess;

        public Funnel(BigDecimal waterRate, Integer capacity) {
            this.waterRate = waterRate;
            this.capacity = capacity;
            this.leftCapacity = capacity;
            this.lastAccess = System.currentTimeMillis();
        }

        /**
         * water out, space add
         */
        public void runningWater() {
            long nowTime = System.currentTimeMillis();
            long deltaTime = nowTime - lastAccess;
//          单位时间内的出水量(deltaCapacity) =  速率(waterRate) * (当前出水的时间戳ts- 上一次出水的时间戳ts)
            Integer deltaCapacity = waterRate.multiply(new BigDecimal(deltaTime)).intValue();
            // the deltaCapacity  means how much water are in .
            // if the interval are too long, it will be over Max Integer
            if (deltaCapacity < 0) {
                this.leftCapacity = capacity;
                this.lastAccess = nowTime;
                return;
            }
            if (deltaCapacity < 1) {
            //同时进入，上一次已经计算过，所以直接return
                return;
            }
            //当前剩余空间空间=上一次的剩余容量+流出的水量(腾出的空间)
            this.leftCapacity += deltaCapacity;
            this.lastAccess = nowTime;
            //水已经流空了
            if (this.leftCapacity > this.capacity) {
                this.leftCapacity = this.capacity;
            }
        }

        /**
         *
         * @param waterCapacity 每次进水单位量
         * @return
         * desc：判断是否有剩余空间
         */
        public boolean watering(Integer waterCapacity) {
            //进水方法
            runningWater();
            if (leftCapacity > waterCapacity) {
                this.leftCapacity -= waterCapacity;
                return true;
            }
            return false;
        }

    }

    public boolean allow(String userId, String key, Integer initCapacity, BigDecimal leakRate) {
        String userKey = userId + key;
        //没有则创建再操作value，有直接操作value
        Funnel funnel = funnelMap.computeIfAbsent(userKey, s -> new Funnel(leakRate, initCapacity));
        //每次流入一个单位
        return funnel.watering(1);
    }

    private static AtomicInteger countAllow = new AtomicInteger(0);
    private static AtomicInteger countDisAllow = new AtomicInteger(0);

    public static void main(String[] args) {
        FunnelRateLimiter funnelProblem = new FunnelRateLimiter();
        for (int i = 0; i < 1000; i++) {
            int mark = i;
            new Thread(() -> {
                //流速是1，容器大小为500
                //开启线程的速度 就是流入速度(人流涌入)
                Boolean allow = funnelProblem.allow("zhangsan", "browser", 500, BigDecimal.valueOf(1));
                if (allow) {
                    System.out.println("you can browser!");
                    countAllow.addAndGet(1);
                } else {
                    System.out.println("pls wait !");
                    countDisAllow.addAndGet(1);
                }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("可以浏览 " + countAllow.get() + " 人次," + "等待了 " + countDisAllow.get() + " 人次");
    }


}
